/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import org.latestbit.smartobjects.coders.*;
import org.latestbit.smartobjects.core.*;
import org.latestbit.smartobjects.encrypters.*;
import org.latestbit.smartobjects.exceptions.*;
import org.latestbit.smartobjects.network.*;
import org.latestbit.smartobjects.utils.*;

/**
 * Standard and default implementation if ISmartBus
 * @author abdulla-a
 *
 */
public class SmartBus implements ISmartBus {

	private IManagedNetwork managedNetwork = new ManagedNetwork();
	private IDataEncrypterFactory dataEncrypterFacatory = new StdDataEncrypterFactory();
	private IHandshakeManagerFactory handshakeManagerFactory = new StdHandshakeManagerFactory( this );
	private IPacketFormerFactory packetFormerFactory = new StdPacketFormerFactory();

	private SequenceGenerator sequenceGenerator = new SequenceGenerator(
			1 /* Initial value */, 1 /* Min Value */, 0x0FFFFFFF /* Max Value */
	);
	
	private Map<String, SmartBusClient> clients = new HashMap<String, SmartBusClient>();
	private Map<String, SmartBusServer> servers = new HashMap<String, SmartBusServer>();
		
	private ConnectMode connectMode = ConnectMode.SYNC_CONNECT_MODE;
	
	private static final long DEFAULT_CONNECT_TIME = 30;
	
	private long awaitConnectTime = DEFAULT_CONNECT_TIME;
	
	private static final int AVAILABLE_CPU_UNITS = Runtime.getRuntime().availableProcessors();
	private static final int DEFAULT_THREAD_POOL_INITIAL_CNT = AVAILABLE_CPU_UNITS;
	private static final int DEFAULT_THREAD_POOL_MAX_CNT = AVAILABLE_CPU_UNITS;
	private static final String SMART_BUS_THREAD_POOL_ID = "SMART_BUS_THREAD_POOL";
	
	private IThreadPool threadPool = null;

	private ServerInstancesRegistry serverInstancesRegistry;	
	
	private IEndpointCoordinatorFactory endpointCoordinatorFactory = new StdEndpointCoordinatorFactory();	
	
	public SmartBus() {
		registerStdManagerFactories();
	}

	private void registerStdManagerFactories() {
		this.managedNetwork.getNetworkFactory().setHandshakeManagerFactory(this.handshakeManagerFactory);
		this.managedNetwork.getNetworkFactory().setPacketFormerFactory(this.packetFormerFactory);
	}

	@Override
	public IDataTypesCoder createDataTypesCoder() {
		return new StdDataTypesCoder();
	}
	
	@Override
	public void setManagedNetwork(IManagedNetwork network) {
		this.managedNetwork = network;
	}
	
	@Override
	public IManagedNetwork getManagedNetwork() {
		return this.managedNetwork;
	}	
	
	@Override
	public SequenceGenerator getSequenceGenerator() {
		return this.sequenceGenerator;
	}

	@Override
	public ConnectMode getConnectMode() {
		return this.connectMode ;
	}

	@Override
	public void setConnectMode(ConnectMode mode) {
		this.connectMode = mode;
	}

	@Override
	public void setSyncConnectTimeout(long seconds) {
		this.awaitConnectTime = seconds;
	}

	@Override
	public long getSyncConnectTimeout() {
		return this.awaitConnectTime;
	}	
	
	@Override
	public void destroy() {
		synchronized(clients) {
			for ( SmartBusClient client : clients.values() ) {
				client.destroy();
			}
			clients.clear();
		}
		
		synchronized(servers) {
			for ( SmartBusServer server : servers.values() ) {
				server.destroy();
			}
			servers.clear();
		}
		
		if ( this.serverInstancesRegistry !=null ) {
			this.serverInstancesRegistry.destroy();
			this.serverInstancesRegistry = null;
		}
		
		if(this.threadPool != null) {
			this.threadPool.destroy();
			this.threadPool = null;
		}
		
		if(this.managedNetwork!=null) {
			this.managedNetwork.destroy();
			this.managedNetwork = null;
		}
	}
	
	public void finalize() {
		destroy();
	}
	
	@Override
	public SmartBusClient selectBusClient(IEndpointLocation location) throws IOException, InterruptedException, SmartBusSystemException {
		SmartBusClient result;
		synchronized(clients) {
			String key = location.toStringReference();
			result = clients.get( key );
			if(result == null) {
				// Create a new client
				result = new SmartBusClient(this, managedNetwork.createClient() );
				IEndpointCoordinator coordinator = this.endpointCoordinatorFactory.createCoordinator(location.getCoordinator());
				result.getManagedClient().setEndpointLocation( location, coordinator );
				clients.put(key, result);
			}
			else
				result.addRef();
		}
		return result;
	}

	@Override
	public void returnBusClient(SmartBusClient client) {
		synchronized(clients) {
			for(Map.Entry<String, SmartBusClient> item : clients.entrySet()) {
				if(item.getValue() == client) {
					if(client.releaseRef()) {
						clients.remove( item.getKey() );
					}
					break;
				}
			}
		}
	}

	@Override
	public SmartBusServer selectBusServer(IEndpointLocation location) throws IOException, SmartBusSystemException {
		SmartBusServer result;
		synchronized(servers) {
			String key = location.toStringReference();
			result = servers.get( key );
			if(result == null) {
				// Create a new client
				result = new SmartBusServer(this, managedNetwork.createServer());
				IEndpointCoordinator coordinator = this.endpointCoordinatorFactory.createCoordinator(location.getCoordinator());
				result.getManagedServer().setEndpointLocation( location, coordinator );
				servers.put(key, result);
			}
			else
				result.addRef();
		}
		return result;
	}
	
	@Override
	public void returnBusServer(SmartBusServer server) {
		synchronized(servers) {
			for(Map.Entry<String, SmartBusServer> item : servers.entrySet()) {
				if(item.getValue() == server) {
					if(server.releaseRef()) {
						servers.remove( item.getKey() );
					}
					break;
				}
			}
		}
	}

	@Override
	public synchronized IThreadPool getThreadPool() {
		if(this.threadPool == null)
			this.threadPool = new ThreadPool ( DEFAULT_THREAD_POOL_INITIAL_CNT, DEFAULT_THREAD_POOL_MAX_CNT, SMART_BUS_THREAD_POOL_ID );
		return this.threadPool;
	}

	@Override
	public synchronized IServerInstancesRegistry getServerInstancesRegistry() {
		if(this.serverInstancesRegistry == null) {
			this.serverInstancesRegistry = new ServerInstancesRegistry ();
		}
		return this.serverInstancesRegistry;
	}
	
	@Override
	public IEndpointCoordinatorFactory getCoordinatorFactory() {		
		return endpointCoordinatorFactory;
	}

	@Override
	public void setCoordinatorFactory(IEndpointCoordinatorFactory factory) {
		this.endpointCoordinatorFactory = factory;
	}


	@Override
	public IDataEncrypterFactory getDataEncrypterFactory() {
		return dataEncrypterFacatory;
	}


	@Override
	public void setDataEncrypterFactory(IDataEncrypterFactory factory) {
		this.dataEncrypterFacatory = factory;
		registerStdManagerFactories();
	}


	@Override
	public void setHandshakeManagerFactory(IHandshakeManagerFactory handshakeManagerFactory) {
		this.handshakeManagerFactory = handshakeManagerFactory;
		registerStdManagerFactories();
	}

	@Override
	public IHandshakeManagerFactory getHandshakeManagerFactory() {
		return handshakeManagerFactory;
	}

	@Override
	public IEndpointLocation createEndpointLocation(String referenceURI) throws URISyntaxException {
		return new EndpointLocation(this, referenceURI);
	}	
}
